/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Eclipse Public License (EPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/eclipse-1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.mmtk.policy.compressor;

import org.mmtk.plan.TraceLocal;
import org.mmtk.plan.TransitiveClosure;
import org.mmtk.policy.Space;
import org.mmtk.policy.compressor.Chunk;

import org.mmtk.utility.alloc.BumpPointer;
import org.mmtk.utility.heap.*;
import org.mmtk.utility.options.Options;
import org.mmtk.utility.Constants;
import org.mmtk.utility.Log;

import org.mmtk.vm.VM;

import org.vmmagic.unboxed.*;
import org.vmmagic.pragma.*;

@Uninterruptible 
public final class MarkCopySpace extends Space implements Constants {
  
  /****************************************************************************
   *
   * Class variables
   */
  
  public static final int LOCAL_GC_BITS_REQUIRED = 1;
  public static final int GLOBAL_GC_BITS_REQUIRED = 0;
  public static final int GC_HEADER_WORDS_REQUIRED = 1;
  
  private static final Offset FORWARDING_POINTER_OFFSET = VM.objectModel.GC_HEADER_OFFSET();


  /****************************************************************************
   *
   * Instance variables
   */
  private boolean fromSpace = true;
  
  private boolean VERBOSE = true;
  private boolean VERY_VERBOSE = VERBOSE && false;  

  public boolean isFromSpace() {
    return fromSpace;
  }

  /** fromSpace CopySpace can always move, toSpace will not move during current GC */
  @Override
  public boolean isMovable() {
    return fromSpace;
  }

  /****************************************************************************
   *
   * Initialization
   */

  /**
   * The caller specifies the region of virtual memory to be used for
   * this space.  If this region conflicts with an existing space,
   * then the constructor will fail.
   *
   * @param name The name of this space (used when printing error messages etc)
   * @param pageBudget The number of pages this space may consume
   * before consulting the plan
   * @param fromSpace The does this instance start life as from-space
   * (or to-space)?
   * @param vmRequest An object describing the virtual memory requested.
   */
  public MarkCopySpace(String name, int pageBudget, boolean fromSpace, VMRequest vmRequest) {
    super(name, true, false, vmRequest);
    this.fromSpace = fromSpace;
    
    if (vmRequest.isDiscontiguous()) {
      pr = new MonotonePageResource(pageBudget, this, Chunk.getRequiredMetaDataPages());
    } else {
      VM.assertions._assert(false);
    }
  }

  /**
   * Non-atomic write of forwarding pointer word (assumption, thread
   * doing the set has done attempt to forward and owns the right to
   * copy the object)
   *
   * @param object The object whose forwarding pointer is to be set
   * @param ptr The forwarding pointer to be stored in the object's
   * forwarding word
   */
  @Inline
  public static void setForwardingPointer(ObjectReference object,
                                           ObjectReference ptr) {
    object.toAddress().store(ptr.toAddress(), FORWARDING_POINTER_OFFSET);
  }

  /**
   * Non-atomic clear of forwarding pointer word (assumption, thread
   * doing the set has done attempt to forward and owns the right to
   * copy the object)
   *
   * @param object The object whose forwarding pointer is to be set
   */
  @Inline
  public static void clearForwardingPointer(ObjectReference object) {
    object.toAddress().store(Address.zero(), FORWARDING_POINTER_OFFSET);
  }
  
  /**
   * Non-atomic read of forwarding pointer
   *
   * @param object The object whose forwarding pointer is to be read
   * @return The forwarding pointer stored in <code>object</code>'s
   * header.
   */
  @Inline
  public static ObjectReference getForwardingPointer(ObjectReference object) {
    return object.toAddress().loadObjectReference(FORWARDING_POINTER_OFFSET);
  }

  /**
   * Trace an object under a copying collection policy.
   * If the object is already copied, the copy is returned.
   * 
   * @param trace The trace being conducted.
   * @param object The object to be forwarded.
   * @return The forwarded object.
   */
  @Inline
  public ObjectReference traceForwardObject(TraceLocal trace, ObjectReference object) {
   
    ObjectReference newObject = getForwardingPointer(object);
    if (CompressorMetadata.liveBitSet(object) && !newObject.isNull()) {
      if (VERBOSE) {
        Log.write("[traceForwardObject] processNode: "); Log.write(object); Log.writeln();
        Log.write("[traceForwardObject] clearing live bit"); Log.writeln();
      }
      // Clear live bit so that next steps in the algorithm don't touch this object
      CompressorMetadata.clearLiveBits(object);
      if(VM.VERIFY_ASSERTIONS) {
        VM.assertions._assert(!isLive(object));
      }
      trace.processNode(object);

      if (VERBOSE) {
        Log.write("forwarding "); Log.write(object);
        Log.write(" -> "); Log.writeln(newObject);
      }

      return newObject;
    }
    
    return object;
  }
  
  
  /****************************************************************************
   *
   * Prepare and release
   */

  /**
   * Prepare this space instance for a collection.  Set the
   * "fromSpace" field according to whether this space is the
   * source or target of the collection. Zero all live bits.
   *
   * @param fromSpace Set the fromSpace field to this value
   */
  public void prepare(boolean fromSpace) { 
    this.fromSpace = fromSpace;
    zeroLiveBits();
  }

  /**
   * Zero all live bits in the metadata part of the chunk
   */
  private void zeroLiveBits() {
    Extent bytes = Extent.fromIntSignExtend(CompressorMetadata.LIVE_BYTES_PER_CHUNK);
  
   for(Address cursor = headDiscontiguousRegion; !cursor.isZero(); cursor = Map.getNextContiguousRegion(cursor)) {
       Address metadata = CompressorMetadata.getLiveMapBase(cursor);;
       VM.memory.zero(metadata, bytes);
     }
  }
  
  /**
   * Release this copy space after a collection.  This means releasing
   * all pages associated with this (now empty) space.
   */
  public void release() {
    ((MonotonePageResource) pr).reset();
    headDiscontiguousRegion = Address.zero();
    fromSpace = false;
  }

  /**
   * Release an allocated page or pages.  In this case we do nothing
   * because we only release pages enmasse.
   *
   * @param start The address of the start of the page or pages
   */
  @Inline
  public void release(Address start) {
    if (VM.VERIFY_ASSERTIONS)
      VM.assertions._assert(false); // this policy only releases pages enmasse
  }

  /****************************************************************************
   *
   * Tracing and forwarding
   */

  /**
   * Mark object as live in the markbit vector and put it for processing
   *
   * @param trace The trace being conducted.
   * @param object The object to be processed.
   * @return The object.
   */
  @Inline
  public ObjectReference traceMarkObject(TransitiveClosure trace, ObjectReference object) {
    if (VM.VERIFY_ASSERTIONS && Options.verbose.getValue() >= 9  && VERY_VERBOSE) {
      Log.write("Tracing "); Log.write(object); Log.writeln();
    }
    
    if (CompressorMetadata.testAndSetLiveBits(object)) {
      if (VM.VERIFY_ASSERTIONS && Options.verbose.getValue() >= 9 && VERY_VERBOSE) {
        Log.write("Putting "); Log.write(object); 
        Log.write(" object for processing"); Log.writeln();
        Log.write("objectStartRef "); Log.write(VM.objectModel.objectStartRef(object)); Log.writeln();
        VM.assertions._assert(isLive(object));
      }
      
      trace.processNode(object);
    }
    
    return object;
  }

  @Override
  public ObjectReference traceObject(TransitiveClosure trace, ObjectReference object) {
    // We should not get here because our code uses other trace methods
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(false);
    return ObjectReference.nullReference();
  }
  
  public static ObjectReference getNewRef(ObjectReference object, Address base) {
    if (VM.VERIFY_ASSERTIONS) {
      VM.assertions._assert(!object.isNull());
      VM.assertions._assert(!base.isZero());      
    }
    
    Address objAddr = object.toAddress();
    if (VM.VERIFY_ASSERTIONS && Options.verbose.getValue() >= 9) {
      Log.write("[getNewReference] ");
      Log.write("OffsetVector for ");
      Log.write(object);
      Log.write(" In ");
      Log.write(CompressorMetadata.getOffsetVectorCellAddress(objAddr));
      Log.writeln();
    }

    int blockOffset = CompressorMetadata.getOffsetVectorCellAddress(objAddr).loadInt();

    if (VM.VERIFY_ASSERTIONS && Options.verbose.getValue() >= 9) {
      Log.write("[getNewReference] ");
      Log.write("blockOffset "); Log.write(blockOffset);
      Log.writeln();
    }

    int liveDataInBlock = Block.liveData(Block.align(objAddr), objAddr);
    Extent offsetInBlock = Extent.fromIntZeroExtend(liveDataInBlock);

    Extent offset = offsetInBlock.plus(blockOffset);
    Address newAddr = base.plus(offset);

    ObjectReference newRef = VM.objectModel.getReferenceWhenCopiedTo(object,
        newAddr);

    if (VM.VERIFY_ASSERTIONS && Options.verbose.getValue() >= 9) {
      Log.write("[getNewReference] ");
      Log.write(object.toAddress()); Log.write("~>"); Log.write(newRef); Log.write(" [newAddr: "); Log.write(newAddr); Log.write("]");
      Log.writeln();
    }
    
    return newRef;
  }
  
  /** 
   * Compute the new reference to an object using Compressor metadata
   * @param trace The trace being conducted.
   * @param object The object to be processed.
   * @return the new reference to object, using Compressor metadata
   */
  public ObjectReference getNewReference(ReferenceFixer trace, ObjectReference object) {
    Address regionBase = trace.getCollector().getBase();
    
    if(VM.VERIFY_ASSERTIONS) {
      VM.assertions._assert(!object.isNull());
      VM.assertions._assert(!regionBase.isZero());
      VM.assertions._assert(BumpPointer.isRegionAligned(regionBase));
    }
    
    Address dataBase = BumpPointer.getDataStart(regionBase);
    return getNewRef(object, dataBase);
  }


  /**
   * Return true if this object is live in this GC
   *
   * @param object The object in question
   * @return True if this object is live in this GC (has it been forwarded?)
   */
  @Override
  public boolean isLive(ObjectReference object) {
    return CompressorMetadata.liveBitSet(object);
  }
  
  /**
   * Has the object in this space been reached during the current collection.
   * This is used for GC Tracing.
   *
   * @param object The object reference.
   * @return True if the object is reachable.
   */
  @Override
  public boolean isReachable(ObjectReference object) {
    return CompressorMetadata.liveBitSet(object);
  }

  /****************************************************************************
   *
   * Header manipulation
   */

  /**
   * Perform any required post-allocation initialization
   *
   * <i>Nothing to be done in this case</i>
   *
   * @param object the object ref to the storage to be initialized
   */
   @Inline
   public void postAlloc(ObjectReference object) {   }
 
}
